语义分割
训练过自己数据集的语义分割模型
但是都是用的别人现成的代码
这一次试一下从头搭一下
数据集制作
之前有自己标过目标检测的数据集,用的工具是labelImg,挺好用的
这一次标语义分割的数据集,用的labelme
在一个目录下,创建imgs文件夹存放原始图片,创建jsons文件夹存放输出
然后在该目录下执行
labelme imgs --output jsons --nodata --autosave
简单来讲就是标注imgs的图片输出到jsons中 然后jsons的输出没有包含imgs的数据 自动保存
具体含义可以通过labelme -h获得
使用ctrl+N就可以开始标注了
如果标注位置相似,例如连续视频帧
可以用ctrl+shift+D,将当前标注的复制到下一张,然后微调即可
标完之后我们只有jsons文件
我们需要通过jsons文件获得marks
labelme自带了labelme_json_to_dataset
可以通过
labelme_json_to_dataset 00000.json --out marks
将00000.json对应的图片转成dataset到marks
生成的文件包括原图片、红色marks、原图和红色marks融合、label.txt文件
但是他有个缺点,就是只能针对一个文件,而且我需要的是二值化白色marks
他的实现也很简单,存放在labelme/cli/json_to_dataset.py中
我根据自己的需求将他的代码修改如下
import argparse
import base64
import json
import os
import os.path as osp
from labelme import utils
import numpy as np
import cv2 as cv
def main():
# python json_to_dataset.py jsons --out masks
# 命令行参数解析
parser = argparse.ArgumentParser()
parser.add_argument("json_file")
parser.add_argument("-o", "--out", default=None)
args = parser.parse_args()
json_dir = args.json_file # json存放目录
file_list = os.listdir(json_dir) # 所有json文件
for file in file_list:
index = os.path.splitext(file)[0] # 文件名 用于后续保存
json_file = os.path.join(json_dir, file) # json路径
# 判断目录是否存在
out_dir = args.out
if not osp.exists(args.out):
os.mkdir(args.out)
# 获取img
data = json.load(open(json_file))
image_path = os.path.join(os.path.dirname(json_file), data["imagePath"])
with open(image_path, "rb") as f:
image_data = f.read()
image_data = base64.b64encode(image_data).decode("utf-8")
img = utils.img_b64_to_arr(image_data)
# 获取label
label_name_to_value = {"_background_": 0, 'line': 1}
lbl, _ = utils.shapes_to_label(
img.shape, data["shapes"], label_name_to_value
) #不同版本可能这里返回值要写成 lbl= 而不是 lbl, _ =
# 转为二值化图像并保存
label = np.array(lbl)
label[np.where(label != 0)] = 255
save_path = os.path.join(out_dir, index + ".png")
cv.imwrite(save_path, label)
print(json_file, ' -> ', save_path) # 打印查看进度
if __name__ == "__main__":
main()
基本是用他的思路 删去不需要的内容 最终读写速度也是比较快的
然后就可以直接对整个文件夹的jsons提取黑白mask到masks文件夹
python json_to_dataset.py jsons --out masks
接下来记录一些比较需要注意的点
图像预处理
用pytorch做计算机视觉经常会接触到Numpy和PIL图像转换,实现如下:
img =Image.fromarray(img) # numpy转PIL
img =np.array(img) # PIL转numpy
但是使用时要注意,Image库读入的时RGB图像,Opencv库读入的时BGR图像
其实仔细一想,numpy可以转为tensor,opencv读入的也是numpy格式
用numpy处理矩阵数据,转换成tensor训练,本来是不需要经过PIL图像的
但是因为预处理常常会用到torchvision里的transfroms,这里的输入是PIL图像
为了避免类型频繁转换,我手动实现了预处理,这样子全过程都不涉及PIL图像
例如下列这个transforms就可以改成
img_transforms = transforms.Compose([
transforms.Resize((256, 256)),
transforms.ToTensor(),
])
就可以改成
def normalized(data):
data_min = np.min(data)
data_max = np.max(data)
if data_max - data_min != 0:
return (data - data_min) / (data_max - data_min)
else:
if data_max != 0:
return data / data_max
else:
return data
img = np.transpose(cv.resize(img, (256, 256)), (2, 0, 1)) # resize 和 通道交换
img_tensor = torch.Tensor(normalized(img))
transpose的用法 :原本是 (w,h,c)对应(0,1,2),需要转成(c,w,h)所以是(2,0,1)
Opencv的索引
因为经常会用到 但是又很容易忘记,记录一下
height, width = output.shape[:2] # shape前两个元素是高和宽
img[row][col] # 图像索引是行和列
cv.circle(show_img, (col, row), 3, (0, 0, 255)) # circle 是列和行